﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using PIT.Labs.SharePoint.Logger;
using PIT.Labs.SharePoint.Extensions;
using PIT.Labs.ProjectRepository.WebParts.ListRandomizerWebPart.ListRandomizerWebPart;
using System.Collections.ObjectModel;
using System.Globalization;
using PIT.Labs.SharePoint.Extensions.Helper;

namespace PIT.Labs.ProjectRepository.WebParts.ListRandomizerWebPart
{
    /// <summary>
    /// Defines the work-items for randomizing the rights of list items. This class works with the timer job (SPTimerService) to process work items (SPWorkItem instances).
    /// </summary>
    /// <seealso cref="T:Microsoft.SharePoint.Administration.SPWorkItemJobDefinition"/>
    class RandomizerTimerJob : Microsoft.SharePoint.Administration.SPWorkItemJobDefinition
    {
        /// <summary>
        /// A globally unique identifier (GUID) that identifies the type.
        /// </summary>
        public static readonly Guid WorkItemID = new Guid("66FE6501-88FE-47A5-BB4F-2049865D2B14");

        /// <summary>
        /// Initializes a new instance.
        /// </summary>
        public RandomizerTimerJob()
            : base()
        {
        }

        /// <summary>
        /// Initializes a new instance that specifies the name of the work item job definition and the associated Web application.
        /// </summary>
        /// <param name="name">The name of the work item job definition instance.</param>
        /// <param name="webApp">The Web application that is associated with the work item job definition.</param>
        public RandomizerTimerJob(String name, SPWebApplication webApp)
            : base(name, webApp)
        {
        }

        /// <summary>
        /// Returns a globally unique identifier (GUID) that identifies the type of a specified work item (SPWorkItem) or work-item batch.
        /// </summary>
        /// <returns>Returns a GUID that specifies the type (Type) of a specified work item (SPWorkItem).</returns>
        public override Guid WorkItemType()
        {
            return WorkItemID;
        }

        /// <summary>
        /// Launches the processing of work items and specifies the content database, the work item collection that contains the work items, and the current state of the pausable timer jobs.
        /// </summary>
        /// <param name="contentDatabase">The SPContentDatabase instance that contains the work item.</param>
        /// <param name="workItems">The work item collection (SPWorkItemCollection instance) that contains the work item to process.</param>
        /// <param name="jobState">The current state of the pausable timer job as represented by the SPJobState instance.</param>
        /// <returns><b>true</b> if the timer job should continue processing the work item.</returns>
        protected override bool ProcessWorkItems(SPContentDatabase contentDatabase,
            SPWorkItemCollection workItems, SPJobState jobState)
        {
            return base.ProcessWorkItems(contentDatabase, workItems, jobState);
        }

        /// <summary>
        /// Launches the processing of a specific work item and specifies the content database, the work item collection that contains the work item, the specific work item itself, and the current state of the pausable timer job.
        /// </summary>
        /// <param name="contentDatabase">The SPContentDatabase instance that contains the work item.</param>
        /// <param name="workItems">The work item collection (SPWorkItemCollection instance) that contains the work item to process.</param>
        /// <param name="workItem">The specific SPWorkItem instance to process.</param>
        /// <param name="jobState">The current state of the pausable timer job as represented by the SPJobState instance.</param>
        /// <returns><b>true</b> if the timer job should continue processing the work item.</returns>
        protected override bool ProcessWorkItem(SPContentDatabase contentDatabase,
            SPWorkItemCollection workItems, SPWorkItem workItem, SPJobState jobState)
        {
            SPLogger logger = new SPLogger(this.WebApplication);
            try
            {
                if (workItem != null)
                {
                    StringBuilder infoMessage = new StringBuilder(String.Empty);
                    infoMessage.AppendLine("Processing work item:");
                    infoMessage.AppendLine("                   Batch ID: " + workItem.BatchId);
                    infoMessage.AppendLine("                   ID: " + workItem.Id);
                    infoMessage.AppendLine("                   Item GUID: " + workItem.ItemGuid);
                    infoMessage.AppendLine("                   Item ID: " + workItem.ItemId);
                    infoMessage.AppendLine("                   Parent ID: " + workItem.ParentId);
                    infoMessage.AppendLine("                   Site ID: " + workItem.SiteId);
                    infoMessage.AppendLine("                   Sub Type: " + workItem.SubType);
                    infoMessage.AppendLine("                   Text Payload: " + workItem.TextPayload);
                    infoMessage.AppendLine("                   Type: " + workItem.Type);
                    infoMessage.AppendLine("                   User ID: " + workItem.UserId);
                    infoMessage.AppendLine("                   Web ID: " + workItem.WebId);
                    logger.Log(NLog.LogLevel.Info, infoMessage.ToString());
                    RandomizeListItems(workItem);
                }
            }
            catch (Exception ex)
            {
                logger.Log(NLog.LogLevel.Error, SPUtilityHelper.GetLocalizedString("ERR008", ListRandomizerWebPart.ListRandomizerWebPart.RESOURCES_FILE), ex, false);
                try
                {
                    using (SPSite site = new SPSite(workItem.SiteId))
                    {
                        using (SPWeb web = site.OpenWeb(workItem.WebId))
                        {
                            ResetPermissions(web.Lists[workItem.ParentId]);
                        }
                    }
                }
                catch (Exception ex2)
                {
                    logger.Log(NLog.LogLevel.Error, SPUtilityHelper.GetLocalizedString("ERR009", ListRandomizerWebPart.ListRandomizerWebPart.RESOURCES_FILE), ex2, false);
                }
            }
            finally
            {
                using (SPSite site = new SPSite(workItem.SiteId))
                {
                    using (SPWeb web = site.OpenWeb(workItem.WebId))
                    {
                        workItems.SubCollection(site, web, 0,
                                (uint)workItems.Count).DeleteWorkItem(workItem.Id);
                    }

                }
            }

            // returning true allows the timer job to process additional items in the queue
            // returning false will end this occurance of the timer job
            return true;
        }

        private void RandomizeListItems(SPWorkItem workItem)
        {
            lock (workItem)
            {
                using (SPSite site = new SPSite(workItem.SiteId))
                {
                    using (SPWeb web = site.OpenWeb(workItem.WebId))
                    {
                        SPList list = web.Lists[workItem.ParentId];
                        String key = typeof(RandomizerTimerJob).FullName + "_" + list.ID.ToString("D", CultureInfo.InvariantCulture);
                        int maxItemCount = workItem.ItemId;
                        Collection<int> oldShuffledItems = web.AllProperties.GetFromXml<Collection<int>>(key);
                        Collection<int> newShuffledIds = Randomizer.Shuffle(list.Items.Count, maxItemCount);

                        bool resetted = false;
                        //Reset, if something is obscure
                        if (oldShuffledItems == null || oldShuffledItems.Count != newShuffledIds.Count)
                        {
                            ResetPermissions(list);
                            resetted = true;
                        }

                        for (int i = 0; i < maxItemCount; i++)
                        {
                            if (!resetted)
                            {
                                list.Items[oldShuffledItems[i]].UnsafeResetRoleInheritance();
                                GrantPermissionToItem(list, newShuffledIds[i]);
                            }
                            else
                            {
                                GrantPermissionToItem(list, newShuffledIds[i]);
                            }
                            list.UnsafeUpdate();
                        }

                        web.AllProperties.Remove(key);
                        web.AllProperties.AddAsXml(key, newShuffledIds);
                        web.UnsafeUpdate();
                    }
                }
            }
        }

        private void GrantPermissionToItem(SPList list, int itemIndex)
        {
            SPRoleDefinition roleDefinition = list.ParentWeb.RoleDefinitions.GetByType(SPRoleType.Reader);
            SPRoleAssignment roleAssignment = new SPRoleAssignment(list.ParentWeb.AssociatedMemberGroup);
            roleAssignment.RoleDefinitionBindings.Add(roleDefinition);
            list.Items[itemIndex].UnsafeBreakRoleInheritance(false);
            bool oldState = list.ParentWeb.AllowUnsafeUpdates;
            list.ParentWeb.AllowUnsafeUpdates = true;
            list.Items[itemIndex].RoleAssignments.Add(roleAssignment);
            list.ParentWeb.AllowUnsafeUpdates = oldState;
        }

        private void ResetPermissions(SPList elevatedList)
        {
            foreach (SPListItem item in elevatedList.Items)
            {
                if (item.HasUniqueRoleAssignments)
                {
                    item.UnsafeResetRoleInheritance();
                }
            }
        }

    }
}
